Executive Summary

The report attempts to analyse and visualize data gathered in Wuhan Hospital during the ongoing pandemic Firstly, the report tries to introduce reader to the demographics of Covid-19 patients. Then the correlation of blood parameters is visualized in both conventional and unconventional ways, through creation of a graph from the correlation matrix. Furthermore the report tackles the change of blood parameters throughout the patients stay in the hospital. Finally, attempt at multi-feature visualization is made, preceding 3-D visualization of a machine learning model results.
In the process both interesting and obvious patterns were discovered.
Surprising correlation was found between aspartate aminotransferase and glutamic-pyruvic transaminase. The discovery of the role of albumin as a predictor of patient outcome was also unexpected. We have also trained a Supported Vector Clasifier, which had around 96% accuracy. However, later we realized that it discovered some well known in medical field predictors.

Demographics of COVID patients

Before we dive into blood sample measures, lets look at the age and gender of people, who survived or died during the hospitalization. We divide patients into 4 age groups. We chose the separations through analysis of slopes of age distribution graphs visualized using violin plots. 4 groups were selected: “0-35”, “35-60”, “60-75”, “75+”.

Data processing:

wuhan_sample = read.csv("wuhan_blood_sample_data_Jan_Feb_2020.csv", sep=";")
last_id = NA
for(row_num in 1:nrow(wuhan_sample)){
  if(is.na(wuhan_sample[row_num, "PATIENT_ID"])){
    wuhan_sample[row_num, "PATIENT_ID"] <- last_id
  }else{
    last_id <- wuhan_sample[row_num, "PATIENT_ID"]
  }
}

wuhan_sample$outcome <- factor(wuhan_sample$outcome, labels=c("survived", "died"))
wuhan_sample$gender <- factor(wuhan_sample$gender, labels = c("male", "female"))
wuhan_complete_sample = wuhan_sample[,]

Demographics visualized in an alternative way

age_gender_outcome <- wuhan_complete_sample %>%
    group_by(PATIENT_ID) %>%
    dplyr::slice(1) %>%
    ungroup() %>%
    select(PATIENT_ID,age,gender, outcome) %>%
  mutate(age_cat=cut(age, breaks=c(-Inf,35, 60, 75, Inf), labels=c("0-35","35-60","60-75", "75+"))) %>%
  dplyr::select(-age) %>%
  count(age_cat,gender, outcome) %>%
  ungroup()


ggplot(data = age_gender_outcome,aes(axis1 = age_cat, axis2 = gender,y = n)) +
  scale_x_discrete(limits = c("Age category", "Gender"), expand = c(.2, .15)) +
  xlab("Demographic") +
  geom_alluvium(aes(fill = outcome)) +
  scale_fill_manual(values=c("#1a9641", "#d7191c")) +
  geom_stratum() +
  geom_text(stat = "stratum", aes(label = after_stat(stratum))) +
  theme_minimal() +
  ggtitle("People who were infected by COVID by Age and Gender") +
  ylab("Number of people")

By looking at this data we can see that the survival rate is decreasing with age for both males and females. Females are generally less likely to die. However this difference is decreasing with age, and is marginal for 75+ group.

Analysis of correlations

The goal of this section is to properly visualize correlations of many features, and to divide them in highly correlated groups.

Correlation with outcome

First lets check which parameters are most correlated with the patient’s outcome. It means that we want to display all features with absolute value of correlation > 0.6. In order to do that we will preprocess data a little bit. We are going to remove time related columns, because we are not interested in them. Than we group measures by PATIENT_ID and calculate the average. Than we remove attributes that contain too much NA values. In the next step we calculate correlation of attributes and select those that satisfy previously mentioned requirements.

wuhan_grouped <- wuhan_complete_sample[,]
wuhan_grouped$outcome <- as.numeric(wuhan_grouped$outcome)
wuhan_grouped$gender <- as.numeric(wuhan_grouped$gender)
wuhan_grouped <- wuhan_grouped %>%
  select(-c(Admission.time, Discharge.time, RE_DATE)) %>%
  group_by(PATIENT_ID) %>%
  summarise_all(mean, na.rm=TRUE) %>%
  select(-PATIENT_ID)

wuhan_grouped <- wuhan_grouped[ , colSums(is.na(wuhan_grouped)) < 60]
# Correlations with outcome
correlations = cor(wuhan_grouped, use="pairwise.complete.obs")
single_cor = data.frame(correlations["outcome",])
single_cor <- cbind(test_name = rownames(single_cor), single_cor)
rownames(single_cor) <- 1:nrow(single_cor)
colnames(single_cor)[colnames(single_cor) == 'correlations..outcome....'] <- 'correlation'

single_cor = single_cor %>% filter(test_name != "outcome") %>% filter(abs(correlation) > 0.6) %>% arrange(abs(correlation))

We obtain the following results:

ggplot(single_cor, aes(x = correlation, y = reorder(test_name, correlation), fill = correlation > 0)) +
  geom_col() +
  ylab("Blood test name") +
  scale_fill_manual(labels = c("Survival", "Death"), values=c(COLOR_GREEN, COLOR_RED)) +
  guides(fill=guide_legend(title="Correlation")) + theme_minimal()

From the following chart we can see that “neutrophils”, “High.sensitivity.C.reactive.protein”, “Lactate.dehydrogenase”, “neutrophilis.count”, “D.D.dimer” are highly positively correlated with death of patients, so patients, who had higher levels of those attributes were more likely to die. We can also see that patients who had higher levels/(*appropriate levels) of “calcium”, “Prothrombin.activity”, “albumin”, “X…lymphocyte” were more likely to survive.

Most correlated variables

Now, we will visualize correlations between most correlated blood tests. We selected them by checking if their maximum correlation with other variables is >= 0.8. If not we will ignore them.

want_ids = c()
for(row in 1:nrow(correlations)) {
  if(abs(max(correlations[row,-row]))>=0.8){
    want_ids = c(want_ids,row)
  }
}
only_high_correlations = correlations[want_ids,want_ids]

Now we will visualize them using heatmap, made using correlation matrix. We made it interactive so that user could check exact values of correlations.

dd <- as.dist((1-only_high_correlations)/2)
hc <- hclust(dd)
only_high_correlations <-only_high_correlations[hc$order, hc$order]
only_high_correlations[upper.tri(only_high_correlations)] <- NA
gathererd = gather(only_high_correlations, na.rm = TRUE)
colnames(gathererd)<-c("1st Variable::", "2nd Variable")
p2 <- ggplot(data = gather(only_high_correlations), aes(x=Var1, y=Var2, fill=value))+
  theme_minimal() +
  geom_tile(color="white") +
  scale_fill_gradient2(low = COLOR_GREEN, high = COLOR_RED, mid = "white",
  midpoint = 0, limit = c(-1,1), space = "Lab",
  name="Correlations") +
  theme(axis.text.x = element_text(angle = 45, vjust = 0.5, hjust=1))
ggplotly(p2) %>% config(displayModeBar = F) %>% layout(xaxis=list(fixedrange=TRUE)) %>% layout(yaxis=list(fixedrange=TRUE))

From this chart we can easily identify pairs or even groups of highly correlated features. We have chosen less than 20 highly correlated features. If we wanted to show more attributes, chart would become unreadable.

Correlation visualization using graphs for many variables

After many hours of trial and error, we concluded that the best way to visualize correlations between attributes would be to use graph. (Those with vertices and edges). First we needed to prepare data. We are using the same data as in previous examples. Here we use threshold for absolute value of correlation > 0.6, because it does not really make sense to show lower correlations. From those correlations we created a graph, where nodes are attributes, and edges are correlations. The stronger the correlation the thicker the line connecting two nodes. Additionally we marked all correlations below |0.6| with dashed lines. We clustered nodes using Louvain Community Detection algorithm. Every group has a different color. Those are our results:

THRESHOLD <- 0.6
MULTIPLIER <- 3
outcome_id <- which(colnames(correlations) == "outcome")
correlations_for_graph = correlations[-outcome_id,-outcome_id]


want_ids = c()
for(row in 1:nrow(correlations_for_graph)) {
  if(abs(max(correlations_for_graph[row,-row]))>THRESHOLD){
    want_ids = c(want_ids,row)
  }
}
correlations_for_graph = correlations_for_graph[want_ids,want_ids]
diag(correlations_for_graph)<-0
correlations_for_graph[lower.tri(correlations_for_graph)] <- 0

nodes <- data.frame(colnames(correlations_for_graph), stringsAsFactors = FALSE)
nodes <- cbind(nodes,nodes)
colnames(nodes) <- c("id", "label")

edges <- gather.matrix(correlations_for_graph)
colnames(edges) <- c("from", "to", "correlation")

edges <- edges[abs(edges$correlation) >THRESHOLD,]

edges$width <- round(abs(edges$correlation*10))
edges$label <- round(edges$correlation,2)
edges$dashes = edges$correlation < 0.8
graph <- graph_from_data_frame(edges, directed = FALSE)

#Louvain Comunity Detection
cluster <- cluster_louvain(graph)

cluster_df <- data.frame(as.list(membership(cluster)))
cluster_df <- as.data.frame(t(cluster_df))
cluster_df$label <- rownames(cluster_df)

#Create group column
nodes <- left_join(nodes, cluster_df, by = "label")
colnames(nodes)[3] <- "group"

nodes <- arrange(nodes,group)

edges$width <- round((((abs(edges$correlation) - THRESHOLD)/(1 - THRESHOLD)+0.3)*MULTIPLIER)^(1.5))

#edges legend
ledges <- data.frame(color = c("black", "blck"),
 label = c("high correlation", "mid correlation"),
 dashes=c(FALSE,TRUE),
 arrows=c("",""),
 width=c(3,3),
 font.align = "top")

visNetwork(nodes, edges, background = "white") %>%
  visIgraphLayout(layout = "layout.fruchterman.reingold", randomSeed = 11) %>%
  visNodes(shadow = TRUE, font = list( size=30,strokeWidth=1, strokeColor ="white")) %>%
  visEdges(title = "<p>hello</p>", ) %>%
  visOptions(highlightNearest = list(enabled = T, degree = 1, hover = T), nodesIdSelection = TRUE) %>%
  visLegend(addEdges = ledges, useGroups = FALSE, main = "Legend", zoom = FALSE)

Please note: That nodes and the whole graph can be moved by clicking it, also if one zooms in, she/he can see correlation values between each nodes. From above graph we can see why all 3: “neutrophils”, “neutrophilis.count”, “X…lymphocyte”, were highly correlated with outcome - they are also highly correlated with each other.

We think that is a somewhat beautiful way to visualize some of the connection between elements of the blood. Some relations are obvious (hemoglobin and hematocrit, serum chloride and serum sodium), but some where unexpected like the relation between: aspartate aminotransferase and glutamic-pyruvic transaminase presumably connected to the balance of the amine ions.

Analysis of parameter’s change in time

Now we would like to focus how patient’s blood parameters changed throughout their stay in the hospital. To do that we average patients’ blood results over consecutive days since admission.

import pandas as pd
df = pd.read_csv("wuhan_blood_sample_data_Jan_Feb_2020.csv", sep=";")
df.PATIENT_ID.fillna(method='ffill', inplace=True)
df['Admission time'] = pd.to_datetime(df['Admission time'])
df['Discharge time'] = pd.to_datetime(df['Discharge time'])
df['Duration of stay'] = df['Discharge time'] - df['Admission time']
df['Duration of stay'] = df['Duration of stay'].dt.days
df.insert(0, "days_from_admission", df["RE_DATE"])
df["days_from_admission"] = pd.to_datetime(df["days_from_admission"])
df["days_from_admission"] = (df["days_from_admission"] - df["Admission time"]).dt.days
df = df.groupby(['PATIENT_ID', 'days_from_admission'], as_index=False).mean()

Side note: It would be better to first group patients that have similar levels of all futures on admission in the hospital using some kind of a clustering algorithm, so that we would compare patient that were in the same stage of Covid-19 infection on admission. Unfortunately we had not enough time to do this, but we are aware of the problem.

Visualizing the change

Plotting function:

import seaborn as sns
import matplotlib.pyplot as plt
# dead_patients = df[df.outcome == 1]
# alive_patients = df[df.outcome == 0]

def plot_triple_plot(y, up_ref, lower_ref, df, unit):
    a4_dims = (35, 30)

    fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=a4_dims)
    plt.rc('legend', fontsize=30)
    plt.rcParams['legend.title_fontsize'] = 30

    palette = ["green", "red"]
    box_plot = sns.boxplot(data=df, x="days_from_admission", y=y, hue="outcome", showfliers=False, ax=ax1,
                           palette=palette)
    box_plot.legend_.set_title("Hospitality outcome:")
    new_labels = ['Alive', 'Dead']
    for t, l in zip(box_plot.legend_.texts, new_labels):
        t.set_text(l)

    swarm_plot = sns.swarmplot(data=df, x="days_from_admission", y=y, hue="outcome",
                               dodge=True, size=2.25, ax=ax2, palette=palette)
    swarm_plot.legend_.set_title("Hospitality outcome:")

    for t, l in zip(swarm_plot.legend_.texts, new_labels):
        t.set_text(l)
    line_plot = sns.lineplot(data=df, x="days_from_admission",
                             y=y, hue="outcome", ci=95, palette=palette, ax=ax3)

    line_plot.collections[1].set_label('95% Confidence interval')
    line_plot.collections[0].set_label("95% Confidence interval")
    line_plot.legend()
    line_plot.legend_.set_title("Hospitality outcome:")

    for t, l in zip(line_plot.legend_.texts, new_labels):
        t.set_text(l)

    line_plot.set(xticks=range(0, 17))
    swarm_plot.set(xticks=range(0, 17))
    box_plot.set(xticks=range(0, 17))

    line_plot.set(xlim=(-0.5, 16))
    box_plot.set(xlim=(None, 16.5))
    swarm_plot.set(xlim=(None, 16.5))

    # Changing ticks size:
    ax1.tick_params(axis='both', labelsize=25)
    ax2.tick_params(axis='both', labelsize=25)
    ax3.tick_params(axis='both', labelsize=25)

    # Possibly there is a better way to do this:
    ax3.set(xlabel=None, ylabel=None)
    ax1.set(xlabel=None, ylabel=None)
    ax2.set(xlabel=None, ylabel=None)
    ax2.set_ylabel(str(y) + " " + f"[{str(unit)}]", fontsize=40)
    ax2.set_xlabel(f"Blue dashed line marks lower and upper reference values for {str(y)} levels in blood.",
                   fontsize=20)
    ax3.set_xlabel("Days from admission [num_of_days]", fontsize=40)

    # Plotting reference values
    ax2.axhline(up_ref, ls='--', color="cornflowerblue")
    ax2.axhline(lower_ref, ls='--', color="cornflowerblue")

    # Showing the plot
    plt.show()

df = df.groupby(['PATIENT_ID', 'days_from_admission'], as_index=False).mean()
df.days_from_admission = df.days_from_admission.apply(lambda x: int(x))

Plotting function visualizes given blood parameter change over time. The values of the parameter are averaged every consecutive day from admission. The figure consists of three charts: boxplot chart, swarm-plot chart, and line-plot. The decision was made to show all three forms, because single plot can be very misleading, due to lack of data. (Especially box plots ticks generated for example for only couple of data points.) Such figure can be generated for any blood parameter present in the data, but we show only some of the most interesting ones:

plot_triple_plot("hemoglobin", 130, 175, df, unit="g/L")

One would expect that patients infected with Covid-19 would have higher hemoglobin concentration to compensate for damaged lung’s tissue. We couldn’t make such observation.

plot_triple_plot("(%)lymphocyte", 10, 45, df, unit="%")

This seems to be pretty straight forward, the more lymphocyte responsible for humoral immune response the patient have the more likely he is to survive.

plot_triple_plot("calcium", 2.12, 2.62, df, unit="mmol/l ")

This observation was also expected, as calcium ions play a very important role in the immune response.

plot_triple_plot("albumin", 35, 55, df, unit="g/L")

This is very shocking, as the protein albumin is not directly connected to immune-response.
We found similar observation suggesting that albumin levels are a very good indicator of the patient’s health condition. Found article proposed even albumin injections. Link to the found article

Trying to visualize more than two features

Scatter plot seem to be simple, but it was able to provide valuable insights:

Not that simple scatter plots

Plotting function:

def plot(x, y, size, sizes, new_labels, x_lim, title, df):
    a4_dims = (17, 8.27)
    fig, ax = plt.subplots(figsize=a4_dims)
    palette = ["green", "red"]
    s = sns.scatterplot(data=df, x=x,
                        y=y, hue="outcome",
                        size=size, sizes=sizes,
                        palette=palette, ax=ax)
    s.set(xlim=(x_lim, None))
    plt.legend(loc=1, prop={'size': 15})
    for text, new_label in zip(s.legend_.texts, new_labels):
        text.set_text(new_label)
    plt.title(title, fontsize=22)
    plt.xlabel(xlabel=x, fontsize=18)
    plt.ylabel(ylabel=y, fontsize=18)
    plt.show()

The figure shows the relation between 3 features and the patient outcome. (x-axis, y-axis, size of markers, and color of markers)

x, y, size = "neutrophils count", "(%)lymphocyte", "albumin"
sizes = (30, 100)
new_labels = ['Hospitality outcome:', 'Alive', 'Dead',
              'Albumin levels:', 'below norm', 'in norm']
x_lim = 0
title = None
plot(x, y, size, sizes, new_labels, x_lim, title, df)

Patients with low lymphocyte levels, high neutrophils count and albumin levels below the norm are almost certain to die.

x, y, size = "Lactate dehydrogenase", "eGFR", "High sensitivity C-reactive protein"
sizes = (20, 150)
new_labels = ['Hospitality outcome:', 'Alive', 'Dead',
              'High sensitivity \nC-reactive protein levels:']
x_lim = 75
title = None
plot(x, y, size, sizes, new_labels, x_lim, title, df)

This is somewhat unexpected, as C-reactive protein being the part of the complement system of the immune system seems to play a role in the immune response, but the rise of it’s level seems to lead to death. Lactate dehydrogenase is a also a good outcome indicator.

Creating a simple model predicting the outcome

Now we are trying to create a model that predicts the patient outcome, we have picked 3 features (mentioned above) that seemed to be the most important.

Support Vector Machine model

We picked this model for the following reason:
Results of the model can be visualized using a hyper-plane.

We are aware that predicting patient outcome based on these parameters is somewhat unproductive as these parameters are well known deteriorating health condition predictors in the medical community. This is more to learn data visualization than to create a useful machine-learning application.

Data preprocessing:

import numpy as np
xx = df['Lactate dehydrogenase']
yy = df['eGFR']
zz = df['High sensitivity C-reactive protein']
hue = df.outcome
hue = ["red" if outcome == 1 else "green" for outcome in np.array(hue)]
df = df.groupby("PATIENT_ID").mean()
df = df.drop(columns=["days_from_admission"])
df = df[df['Lactate dehydrogenase'].notna()]
df = df[df['eGFR'].notna()]
df = df[df["High sensitivity C-reactive protein"].notna()]
X = df[["Lactate dehydrogenase", "eGFR", "High sensitivity C-reactive protein"]]
y = df.outcome

Training the model:

from sklearn.model_selection import train_test_split
from sklearn import svm
# Splitting data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.33)

# Creating and fitting the model
model = svm.SVC(kernel='linear', random_state=2)
clf = model.fit(X_train, y_train)
score = model.score(X_test, y_test)
print("%0.3f" % score)
0.949

The model score is very high ( There is a pretty good chance that we over-fit)

3-D visualization of the model results

Now we try to visualize the plane that divides points described by the three given features. This was not fun, and now we understand in full why is it better to stay away from 3D. Unfortunately, r-markdown seems to disable interactivity of the plot. (The figure can be moved in jupyter environment).

# This is not as intuitive as I thought.
temp = np.linspace(0, 750, 50)
x, y = np.meshgrid(temp, temp)

# https://stackoverflow.com/users/1430829/matt-hancock Thanks to limitless knowledge of Matt Hancock.
z = lambda x, y: (-clf.intercept_[0] - clf.coef_[0][0] * x - clf.coef_[0][1] * y) / clf.coef_[0][2]

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
t = ax.scatter(xx, yy, zz, c=hue)
ax.plot_surface(x, y, z(x, y))
ax.set_xlabel('Lactate dehydrogenase')
ax.set_ylabel('eGFR')
ax.set_zlabel('High sensitivity C-reactive protein')
dummy = ax.set_xlim3d(0, None)
dummy = ax.set_ylim3d(0, None)
dummy = ax.set_zlim3d(0, None)
ax.view_init(21, -133)  # 26, 45 #25 -124
ax.set_title("Green points - alive patients, red points - dead patients,\n "
             "blue plane based on Support Vector Machine model.")
plt.show()

The plane divides red and green points very efficiently, visualizing graphically the model.

Thank you for reading.

Sources:

LS0tDQp0aXRsZTogIDxjZW50ZXI+V3VoYW4gRGF0YSBSZXBvcnQ8L2NlbnRlcj4NCiAgIVtdKFBQX2xvZ28uanBnKQ0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIHRvYzogdHJ1ZQ0KYXV0aG9yOiBKYW4gR3J1c3pjenluc2tpICYgS2FjcGVyIFRyZWJhY3oNCmRhdGU6IDE4LjA0LjIwMjENCi0tLQ0KDQojIEV4ZWN1dGl2ZSBTdW1tYXJ5DQpUaGUgcmVwb3J0IGF0dGVtcHRzIHRvIGFuYWx5c2UgYW5kIHZpc3VhbGl6ZSBkYXRhIGdhdGhlcmVkIGluIFd1aGFuIEhvc3BpdGFsIGR1cmluZyB0aGUgb25nb2luZyBwYW5kZW1pYyBGaXJzdGx5LCB0aGUgcmVwb3J0IHRyaWVzIHRvIGludHJvZHVjZSByZWFkZXIgdG8gdGhlIGRlbW9ncmFwaGljcyBvZiBDb3ZpZC0xOSBwYXRpZW50cy4NClRoZW4gdGhlIGNvcnJlbGF0aW9uIG9mIGJsb29kIHBhcmFtZXRlcnMgaXMgdmlzdWFsaXplZCBpbiBib3RoIGNvbnZlbnRpb25hbCBhbmQgdW5jb252ZW50aW9uYWwgd2F5cywgdGhyb3VnaCBjcmVhdGlvbiBvZiBhIGdyYXBoIGZyb20gdGhlIGNvcnJlbGF0aW9uIG1hdHJpeC4gRnVydGhlcm1vcmUgdGhlIHJlcG9ydCB0YWNrbGVzIHRoZSBjaGFuZ2Ugb2YgYmxvb2QgcGFyYW1ldGVycyB0aHJvdWdob3V0IHRoZSBwYXRpZW50cyBzdGF5IGluIHRoZSBob3NwaXRhbC4gRmluYWxseSwgYXR0ZW1wdCBhdCBtdWx0aS1mZWF0dXJlIHZpc3VhbGl6YXRpb24gaXMgbWFkZSwgcHJlY2VkaW5nIDMtRCB2aXN1YWxpemF0aW9uIG9mIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCByZXN1bHRzLiBcDQpJbiB0aGUgcHJvY2VzcyBib3RoIGludGVyZXN0aW5nIGFuZCBvYnZpb3VzIHBhdHRlcm5zIHdlcmUgZGlzY292ZXJlZC5cDQpTdXJwcmlzaW5nIGNvcnJlbGF0aW9uIHdhcyBmb3VuZCBiZXR3ZWVuIGFzcGFydGF0ZSBhbWlub3RyYW5zZmVyYXNlIGFuZCBnbHV0YW1pYy1weXJ1dmljIHRyYW5zYW1pbmFzZS4gVGhlIGRpc2NvdmVyeSBvZiB0aGUgcm9sZSBvZiBhbGJ1bWluIGFzIGEgcHJlZGljdG9yIG9mIHBhdGllbnQgb3V0Y29tZSB3YXMgYWxzbyB1bmV4cGVjdGVkLiBXZSBoYXZlIGFsc28gdHJhaW5lZCBhIFN1cHBvcnRlZCBWZWN0b3IgQ2xhc2lmaWVyLCB3aGljaCBoYWQgYXJvdW5kIDk2JSBhY2N1cmFjeS4gSG93ZXZlciwgbGF0ZXIgd2UgcmVhbGl6ZWQgdGhhdCBpdCBkaXNjb3ZlcmVkIHNvbWUgd2VsbCBrbm93biBpbiBtZWRpY2FsIGZpZWxkIHByZWRpY3RvcnMuDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShHR2FsbHkpDQpsaWJyYXJ5KGdnZm9yY2UpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShyZXNoYXBlMikNCmdhdGhlci5tYXRyaXggPC0gcmVzaGFwZTI6OjptZWx0Lm1hdHJpeA0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGlncmFwaCkNCmxpYnJhcnkoZ2VvbW5ldCkNCmxpYnJhcnkodmlzTmV0d29yaykNCmxpYnJhcnkoZ2dhbGx1dmlhbCkNCkNPTE9SX0dSRUVOID0gIiMxYTk2NDEiDQpDT0xPUl9SRUQgPSAiI2Q3MTkxYyINCmBgYA0KDQoNCiMgRGVtb2dyYXBoaWNzIG9mIENPVklEIHBhdGllbnRzDQpCZWZvcmUgd2UgZGl2ZSBpbnRvIGJsb29kIHNhbXBsZSBtZWFzdXJlcywgbGV0cyBsb29rIGF0IHRoZSBhZ2UgYW5kIGdlbmRlciBvZiBwZW9wbGUsIHdobyBzdXJ2aXZlZCBvciBkaWVkIGR1cmluZyB0aGUgaG9zcGl0YWxpemF0aW9uLiBXZSBkaXZpZGUgcGF0aWVudHMgaW50byA0IGFnZSBncm91cHMuIFdlIGNob3NlIHRoZSBzZXBhcmF0aW9ucyB0aHJvdWdoIGFuYWx5c2lzIG9mIHNsb3BlcyBvZiBhZ2UgZGlzdHJpYnV0aW9uIGdyYXBocyB2aXN1YWxpemVkIHVzaW5nIHZpb2xpbiBwbG90cy4gNCBncm91cHMgd2VyZSBzZWxlY3RlZDogIjAtMzUiLCAiMzUtNjAiLCAiNjAtNzUiLCAiNzUrIi4NCg0KRGF0YSBwcm9jZXNzaW5nOg0KYGBge3J9DQp3dWhhbl9zYW1wbGUgPSByZWFkLmNzdigid3VoYW5fYmxvb2Rfc2FtcGxlX2RhdGFfSmFuX0ZlYl8yMDIwLmNzdiIsIHNlcD0iOyIpDQpsYXN0X2lkID0gTkENCmZvcihyb3dfbnVtIGluIDE6bnJvdyh3dWhhbl9zYW1wbGUpKXsNCiAgaWYoaXMubmEod3VoYW5fc2FtcGxlW3Jvd19udW0sICJQQVRJRU5UX0lEIl0pKXsNCiAgICB3dWhhbl9zYW1wbGVbcm93X251bSwgIlBBVElFTlRfSUQiXSA8LSBsYXN0X2lkDQogIH1lbHNlew0KICAgIGxhc3RfaWQgPC0gd3VoYW5fc2FtcGxlW3Jvd19udW0sICJQQVRJRU5UX0lEIl0NCiAgfQ0KfQ0KDQp3dWhhbl9zYW1wbGUkb3V0Y29tZSA8LSBmYWN0b3Iod3VoYW5fc2FtcGxlJG91dGNvbWUsIGxhYmVscz1jKCJzdXJ2aXZlZCIsICJkaWVkIikpDQp3dWhhbl9zYW1wbGUkZ2VuZGVyIDwtIGZhY3Rvcih3dWhhbl9zYW1wbGUkZ2VuZGVyLCBsYWJlbHMgPSBjKCJtYWxlIiwgImZlbWFsZSIpKQ0Kd3VoYW5fY29tcGxldGVfc2FtcGxlID0gd3VoYW5fc2FtcGxlWyxdDQpgYGANCg0KDQojIyBEZW1vZ3JhcGhpY3MgdmlzdWFsaXplZCBpbiBhbiBhbHRlcm5hdGl2ZSB3YXkNCmBgYHtyfQ0KYWdlX2dlbmRlcl9vdXRjb21lIDwtIHd1aGFuX2NvbXBsZXRlX3NhbXBsZSAlPiUNCiAgICBncm91cF9ieShQQVRJRU5UX0lEKSAlPiUNCiAgICBkcGx5cjo6c2xpY2UoMSkgJT4lDQogICAgdW5ncm91cCgpICU+JQ0KICAgIHNlbGVjdChQQVRJRU5UX0lELGFnZSxnZW5kZXIsIG91dGNvbWUpICU+JQ0KICBtdXRhdGUoYWdlX2NhdD1jdXQoYWdlLCBicmVha3M9YygtSW5mLDM1LCA2MCwgNzUsIEluZiksIGxhYmVscz1jKCIwLTM1IiwiMzUtNjAiLCI2MC03NSIsICI3NSsiKSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1hZ2UpICU+JQ0KICBjb3VudChhZ2VfY2F0LGdlbmRlciwgb3V0Y29tZSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQoNCmdncGxvdChkYXRhID0gYWdlX2dlbmRlcl9vdXRjb21lLGFlcyhheGlzMSA9IGFnZV9jYXQsIGF4aXMyID0gZ2VuZGVyLHkgPSBuKSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IGMoIkFnZSBjYXRlZ29yeSIsICJHZW5kZXIiKSwgZXhwYW5kID0gYyguMiwgLjE1KSkgKw0KICB4bGFiKCJEZW1vZ3JhcGhpYyIpICsNCiAgZ2VvbV9hbGx1dml1bShhZXMoZmlsbCA9IG91dGNvbWUpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjMWE5NjQxIiwgIiNkNzE5MWMiKSkgKw0KICBnZW9tX3N0cmF0dW0oKSArDQogIGdlb21fdGV4dChzdGF0ID0gInN0cmF0dW0iLCBhZXMobGFiZWwgPSBhZnRlcl9zdGF0KHN0cmF0dW0pKSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBnZ3RpdGxlKCJQZW9wbGUgd2hvIHdlcmUgaW5mZWN0ZWQgYnkgQ09WSUQgYnkgQWdlIGFuZCBHZW5kZXIiKSArDQogIHlsYWIoIk51bWJlciBvZiBwZW9wbGUiKQ0KYGBgDQpCeSBsb29raW5nIGF0IHRoaXMgZGF0YSB3ZSBjYW4gc2VlIHRoYXQgdGhlIHN1cnZpdmFsIHJhdGUgaXMgZGVjcmVhc2luZyB3aXRoIGFnZSBmb3IgYm90aCBtYWxlcyBhbmQgZmVtYWxlcy4gRmVtYWxlcyBhcmUgZ2VuZXJhbGx5IGxlc3MgbGlrZWx5IHRvIGRpZS4gSG93ZXZlciB0aGlzIGRpZmZlcmVuY2UgaXMgZGVjcmVhc2luZyB3aXRoIGFnZSwgYW5kIGlzIG1hcmdpbmFsIGZvciA3NSsgZ3JvdXAuDQoNCiMgQW5hbHlzaXMgb2YgY29ycmVsYXRpb25zDQpgYGB7ciwgZWNobz1GQUxTRX0NCk1JRF9DT1JSX1RIUkVTSE9MRCA9IDAuNg0KYGBgDQoNClRoZSBnb2FsIG9mIHRoaXMgc2VjdGlvbiBpcyB0byBwcm9wZXJseSB2aXN1YWxpemUgY29ycmVsYXRpb25zIG9mIG1hbnkgZmVhdHVyZXMsIGFuZCB0byBkaXZpZGUgdGhlbSBpbiBoaWdobHkgY29ycmVsYXRlZCBncm91cHMuDQoNCiMjIENvcnJlbGF0aW9uIHdpdGggb3V0Y29tZQ0KDQpGaXJzdCBsZXRzIGNoZWNrIHdoaWNoIHBhcmFtZXRlcnMgYXJlIG1vc3QgY29ycmVsYXRlZCB3aXRoIHRoZSBwYXRpZW50J3Mgb3V0Y29tZS4gSXQgbWVhbnMgdGhhdCB3ZSB3YW50IHRvIGRpc3BsYXkgYWxsIGZlYXR1cmVzIHdpdGggYWJzb2x1dGUgdmFsdWUgb2YgY29ycmVsYXRpb24gPiAwLjYuIEluIG9yZGVyIHRvIGRvIHRoYXQgd2Ugd2lsbCBwcmVwcm9jZXNzIGRhdGEgYSBsaXR0bGUgYml0LiBXZSBhcmUgZ29pbmcgdG8gcmVtb3ZlIHRpbWUgcmVsYXRlZCBjb2x1bW5zLCBiZWNhdXNlIHdlIGFyZSBub3QgaW50ZXJlc3RlZCBpbiB0aGVtLiBUaGFuIHdlIGdyb3VwIG1lYXN1cmVzIGJ5IFBBVElFTlRfSUQgYW5kIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZS4gVGhhbiB3ZSByZW1vdmUgYXR0cmlidXRlcyB0aGF0IGNvbnRhaW4gdG9vIG11Y2ggTkEgdmFsdWVzLiBJbiB0aGUgbmV4dCBzdGVwIHdlIGNhbGN1bGF0ZSBjb3JyZWxhdGlvbiBvZiBhdHRyaWJ1dGVzIGFuZCBzZWxlY3QgdGhvc2UgdGhhdCBzYXRpc2Z5IHByZXZpb3VzbHkgbWVudGlvbmVkIHJlcXVpcmVtZW50cy4NCg0KYGBge3J9DQp3dWhhbl9ncm91cGVkIDwtIHd1aGFuX2NvbXBsZXRlX3NhbXBsZVssXQ0Kd3VoYW5fZ3JvdXBlZCRvdXRjb21lIDwtIGFzLm51bWVyaWMod3VoYW5fZ3JvdXBlZCRvdXRjb21lKQ0Kd3VoYW5fZ3JvdXBlZCRnZW5kZXIgPC0gYXMubnVtZXJpYyh3dWhhbl9ncm91cGVkJGdlbmRlcikNCnd1aGFuX2dyb3VwZWQgPC0gd3VoYW5fZ3JvdXBlZCAlPiUNCiAgc2VsZWN0KC1jKEFkbWlzc2lvbi50aW1lLCBEaXNjaGFyZ2UudGltZSwgUkVfREFURSkpICU+JQ0KICBncm91cF9ieShQQVRJRU5UX0lEKSAlPiUNCiAgc3VtbWFyaXNlX2FsbChtZWFuLCBuYS5ybT1UUlVFKSAlPiUNCiAgc2VsZWN0KC1QQVRJRU5UX0lEKQ0KDQp3dWhhbl9ncm91cGVkIDwtIHd1aGFuX2dyb3VwZWRbICwgY29sU3Vtcyhpcy5uYSh3dWhhbl9ncm91cGVkKSkgPCA2MF0NCiMgQ29ycmVsYXRpb25zIHdpdGggb3V0Y29tZQ0KY29ycmVsYXRpb25zID0gY29yKHd1aGFuX2dyb3VwZWQsIHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIikNCnNpbmdsZV9jb3IgPSBkYXRhLmZyYW1lKGNvcnJlbGF0aW9uc1sib3V0Y29tZSIsXSkNCnNpbmdsZV9jb3IgPC0gY2JpbmQodGVzdF9uYW1lID0gcm93bmFtZXMoc2luZ2xlX2NvciksIHNpbmdsZV9jb3IpDQpyb3duYW1lcyhzaW5nbGVfY29yKSA8LSAxOm5yb3coc2luZ2xlX2NvcikNCmNvbG5hbWVzKHNpbmdsZV9jb3IpW2NvbG5hbWVzKHNpbmdsZV9jb3IpID09ICdjb3JyZWxhdGlvbnMuLm91dGNvbWUuLi4uJ10gPC0gJ2NvcnJlbGF0aW9uJw0KDQpzaW5nbGVfY29yID0gc2luZ2xlX2NvciAlPiUgZmlsdGVyKHRlc3RfbmFtZSAhPSAib3V0Y29tZSIpICU+JSBmaWx0ZXIoYWJzKGNvcnJlbGF0aW9uKSA+IDAuNikgJT4lIGFycmFuZ2UoYWJzKGNvcnJlbGF0aW9uKSkNCmBgYA0KV2Ugb2J0YWluIHRoZSBmb2xsb3dpbmcgcmVzdWx0czoNCmBgYHtyfQ0KZ2dwbG90KHNpbmdsZV9jb3IsIGFlcyh4ID0gY29ycmVsYXRpb24sIHkgPSByZW9yZGVyKHRlc3RfbmFtZSwgY29ycmVsYXRpb24pLCBmaWxsID0gY29ycmVsYXRpb24gPiAwKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgeWxhYigiQmxvb2QgdGVzdCBuYW1lIikgKw0KICBzY2FsZV9maWxsX21hbnVhbChsYWJlbHMgPSBjKCJTdXJ2aXZhbCIsICJEZWF0aCIpLCB2YWx1ZXM9YyhDT0xPUl9HUkVFTiwgQ09MT1JfUkVEKSkgKw0KICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQodGl0bGU9IkNvcnJlbGF0aW9uIikpICsgdGhlbWVfbWluaW1hbCgpDQpgYGANCkZyb20gdGhlIGZvbGxvd2luZyBjaGFydCB3ZSBjYW4gc2VlIHRoYXQgIm5ldXRyb3BoaWxzIiwgIkhpZ2guc2Vuc2l0aXZpdHkuQy5yZWFjdGl2ZS5wcm90ZWluIiwgIkxhY3RhdGUuZGVoeWRyb2dlbmFzZSIsICJuZXV0cm9waGlsaXMuY291bnQiLCAiRC5ELmRpbWVyIiBhcmUgaGlnaGx5IHBvc2l0aXZlbHkgY29ycmVsYXRlZCB3aXRoIGRlYXRoIG9mIHBhdGllbnRzLCBzbyBwYXRpZW50cywgd2hvIGhhZCBoaWdoZXIgbGV2ZWxzIG9mIHRob3NlIGF0dHJpYnV0ZXMgd2VyZSBtb3JlIGxpa2VseSB0byBkaWUuIFdlIGNhbiBhbHNvIHNlZSB0aGF0IHBhdGllbnRzIHdobyBoYWQgaGlnaGVyIGxldmVscy8oKmFwcHJvcHJpYXRlIGxldmVscykgb2YgImNhbGNpdW0iLCAiUHJvdGhyb21iaW4uYWN0aXZpdHkiLCAiYWxidW1pbiIsICJYLi4ubHltcGhvY3l0ZSIgd2VyZSBtb3JlIGxpa2VseSB0byBzdXJ2aXZlLg0KDQojIyBNb3N0IGNvcnJlbGF0ZWQgdmFyaWFibGVzDQoNCk5vdywgd2Ugd2lsbCB2aXN1YWxpemUgY29ycmVsYXRpb25zIGJldHdlZW4gbW9zdCBjb3JyZWxhdGVkIGJsb29kIHRlc3RzLiBXZSBzZWxlY3RlZCB0aGVtIGJ5IGNoZWNraW5nIGlmIHRoZWlyIG1heGltdW0gY29ycmVsYXRpb24gd2l0aCBvdGhlciB2YXJpYWJsZXMgaXMgPj0gMC44LiBJZiBub3Qgd2Ugd2lsbCBpZ25vcmUgdGhlbS4NCg0KYGBge3J9DQp3YW50X2lkcyA9IGMoKQ0KZm9yKHJvdyBpbiAxOm5yb3coY29ycmVsYXRpb25zKSkgew0KICBpZihhYnMobWF4KGNvcnJlbGF0aW9uc1tyb3csLXJvd10pKT49MC44KXsNCiAgICB3YW50X2lkcyA9IGMod2FudF9pZHMscm93KQ0KICB9DQp9DQpvbmx5X2hpZ2hfY29ycmVsYXRpb25zID0gY29ycmVsYXRpb25zW3dhbnRfaWRzLHdhbnRfaWRzXQ0KYGBgDQpOb3cgd2Ugd2lsbCB2aXN1YWxpemUgdGhlbSB1c2luZyBoZWF0bWFwLCBtYWRlIHVzaW5nIGNvcnJlbGF0aW9uIG1hdHJpeC4gV2UgbWFkZSBpdCBpbnRlcmFjdGl2ZSBzbyB0aGF0IHVzZXIgY291bGQgY2hlY2sgZXhhY3QgdmFsdWVzIG9mIGNvcnJlbGF0aW9ucy4NCmBgYHtyfQ0KZGQgPC0gYXMuZGlzdCgoMS1vbmx5X2hpZ2hfY29ycmVsYXRpb25zKS8yKQ0KaGMgPC0gaGNsdXN0KGRkKQ0Kb25seV9oaWdoX2NvcnJlbGF0aW9ucyA8LW9ubHlfaGlnaF9jb3JyZWxhdGlvbnNbaGMkb3JkZXIsIGhjJG9yZGVyXQ0Kb25seV9oaWdoX2NvcnJlbGF0aW9uc1t1cHBlci50cmkob25seV9oaWdoX2NvcnJlbGF0aW9ucyldIDwtIE5BDQpnYXRoZXJlcmQgPSBnYXRoZXIob25seV9oaWdoX2NvcnJlbGF0aW9ucywgbmEucm0gPSBUUlVFKQ0KY29sbmFtZXMoZ2F0aGVyZXJkKTwtYygiMXN0IFZhcmlhYmxlOjoiLCAiMm5kIFZhcmlhYmxlIikNCnAyIDwtIGdncGxvdChkYXRhID0gZ2F0aGVyKG9ubHlfaGlnaF9jb3JyZWxhdGlvbnMpLCBhZXMoeD1WYXIxLCB5PVZhcjIsIGZpbGw9dmFsdWUpKSsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2VvbV90aWxlKGNvbG9yPSJ3aGl0ZSIpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gQ09MT1JfR1JFRU4sIGhpZ2ggPSBDT0xPUl9SRUQsIG1pZCA9ICJ3aGl0ZSIsDQogIG1pZHBvaW50ID0gMCwgbGltaXQgPSBjKC0xLDEpLCBzcGFjZSA9ICJMYWIiLA0KICBuYW1lPSJDb3JyZWxhdGlvbnMiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkNCmdncGxvdGx5KHAyKSAlPiUgY29uZmlnKGRpc3BsYXlNb2RlQmFyID0gRikgJT4lIGxheW91dCh4YXhpcz1saXN0KGZpeGVkcmFuZ2U9VFJVRSkpICU+JSBsYXlvdXQoeWF4aXM9bGlzdChmaXhlZHJhbmdlPVRSVUUpKQ0KYGBgDQoNCkZyb20gdGhpcyBjaGFydCB3ZSBjYW4gZWFzaWx5IGlkZW50aWZ5IHBhaXJzIG9yIGV2ZW4gZ3JvdXBzIG9mIGhpZ2hseSBjb3JyZWxhdGVkIGZlYXR1cmVzLiBXZSBoYXZlIGNob3NlbiBsZXNzIHRoYW4gMjAgaGlnaGx5IGNvcnJlbGF0ZWQgZmVhdHVyZXMuIElmIHdlIHdhbnRlZCB0byBzaG93IG1vcmUgYXR0cmlidXRlcywgY2hhcnQgd291bGQgYmVjb21lIHVucmVhZGFibGUuDQoNCiMjIENvcnJlbGF0aW9uIHZpc3VhbGl6YXRpb24gdXNpbmcgZ3JhcGhzIGZvciBtYW55IHZhcmlhYmxlcw0KDQpBZnRlciBtYW55IGhvdXJzIG9mIHRyaWFsIGFuZCBlcnJvciwgd2UgY29uY2x1ZGVkIHRoYXQgdGhlIGJlc3Qgd2F5IHRvIHZpc3VhbGl6ZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiBhdHRyaWJ1dGVzIHdvdWxkIGJlIHRvIHVzZSBncmFwaC4gKFRob3NlIHdpdGggdmVydGljZXMgYW5kIGVkZ2VzKS4gRmlyc3Qgd2UgbmVlZGVkIHRvIHByZXBhcmUgZGF0YS4gV2UgYXJlIHVzaW5nIHRoZSBzYW1lIGRhdGEgYXMgaW4gcHJldmlvdXMgZXhhbXBsZXMuIEhlcmUgd2UgdXNlIHRocmVzaG9sZCBmb3IgYWJzb2x1dGUgdmFsdWUgb2YgY29ycmVsYXRpb24gPiAwLjYsIGJlY2F1c2UgaXQgZG9lcyBub3QgcmVhbGx5IG1ha2Ugc2Vuc2UgdG8gc2hvdyBsb3dlciBjb3JyZWxhdGlvbnMuIEZyb20gdGhvc2UgY29ycmVsYXRpb25zIHdlIGNyZWF0ZWQgYSBncmFwaCwgd2hlcmUgbm9kZXMgYXJlIGF0dHJpYnV0ZXMsIGFuZCBlZGdlcyBhcmUgY29ycmVsYXRpb25zLiBUaGUgc3Ryb25nZXIgdGhlIGNvcnJlbGF0aW9uIHRoZSB0aGlja2VyIHRoZSBsaW5lIGNvbm5lY3RpbmcgdHdvIG5vZGVzLiBBZGRpdGlvbmFsbHkgd2UgbWFya2VkIGFsbCBjb3JyZWxhdGlvbnMgYmVsb3cgfDAuNnwgd2l0aCBkYXNoZWQgbGluZXMuIFdlIGNsdXN0ZXJlZCBub2RlcyB1c2luZyBMb3V2YWluIENvbW11bml0eSBEZXRlY3Rpb24gYWxnb3JpdGhtLiBFdmVyeSBncm91cCBoYXMgYSBkaWZmZXJlbnQgY29sb3IuDQogVGhvc2UgYXJlIG91ciByZXN1bHRzOg0KDQpgYGB7cn0NClRIUkVTSE9MRCA8LSAwLjYNCk1VTFRJUExJRVIgPC0gMw0Kb3V0Y29tZV9pZCA8LSB3aGljaChjb2xuYW1lcyhjb3JyZWxhdGlvbnMpID09ICJvdXRjb21lIikNCmNvcnJlbGF0aW9uc19mb3JfZ3JhcGggPSBjb3JyZWxhdGlvbnNbLW91dGNvbWVfaWQsLW91dGNvbWVfaWRdDQoNCg0Kd2FudF9pZHMgPSBjKCkNCmZvcihyb3cgaW4gMTpucm93KGNvcnJlbGF0aW9uc19mb3JfZ3JhcGgpKSB7DQogIGlmKGFicyhtYXgoY29ycmVsYXRpb25zX2Zvcl9ncmFwaFtyb3csLXJvd10pKT5USFJFU0hPTEQpew0KICAgIHdhbnRfaWRzID0gYyh3YW50X2lkcyxyb3cpDQogIH0NCn0NCmNvcnJlbGF0aW9uc19mb3JfZ3JhcGggPSBjb3JyZWxhdGlvbnNfZm9yX2dyYXBoW3dhbnRfaWRzLHdhbnRfaWRzXQ0KZGlhZyhjb3JyZWxhdGlvbnNfZm9yX2dyYXBoKTwtMA0KY29ycmVsYXRpb25zX2Zvcl9ncmFwaFtsb3dlci50cmkoY29ycmVsYXRpb25zX2Zvcl9ncmFwaCldIDwtIDANCg0Kbm9kZXMgPC0gZGF0YS5mcmFtZShjb2xuYW1lcyhjb3JyZWxhdGlvbnNfZm9yX2dyYXBoKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0Kbm9kZXMgPC0gY2JpbmQobm9kZXMsbm9kZXMpDQpjb2xuYW1lcyhub2RlcykgPC0gYygiaWQiLCAibGFiZWwiKQ0KDQplZGdlcyA8LSBnYXRoZXIubWF0cml4KGNvcnJlbGF0aW9uc19mb3JfZ3JhcGgpDQpjb2xuYW1lcyhlZGdlcykgPC0gYygiZnJvbSIsICJ0byIsICJjb3JyZWxhdGlvbiIpDQoNCmVkZ2VzIDwtIGVkZ2VzW2FicyhlZGdlcyRjb3JyZWxhdGlvbikgPlRIUkVTSE9MRCxdDQoNCmVkZ2VzJHdpZHRoIDwtIHJvdW5kKGFicyhlZGdlcyRjb3JyZWxhdGlvbioxMCkpDQplZGdlcyRsYWJlbCA8LSByb3VuZChlZGdlcyRjb3JyZWxhdGlvbiwyKQ0KZWRnZXMkZGFzaGVzID0gZWRnZXMkY29ycmVsYXRpb24gPCAwLjgNCmdyYXBoIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShlZGdlcywgZGlyZWN0ZWQgPSBGQUxTRSkNCg0KI0xvdXZhaW4gQ29tdW5pdHkgRGV0ZWN0aW9uDQpjbHVzdGVyIDwtIGNsdXN0ZXJfbG91dmFpbihncmFwaCkNCg0KY2x1c3Rlcl9kZiA8LSBkYXRhLmZyYW1lKGFzLmxpc3QobWVtYmVyc2hpcChjbHVzdGVyKSkpDQpjbHVzdGVyX2RmIDwtIGFzLmRhdGEuZnJhbWUodChjbHVzdGVyX2RmKSkNCmNsdXN0ZXJfZGYkbGFiZWwgPC0gcm93bmFtZXMoY2x1c3Rlcl9kZikNCg0KI0NyZWF0ZSBncm91cCBjb2x1bW4NCm5vZGVzIDwtIGxlZnRfam9pbihub2RlcywgY2x1c3Rlcl9kZiwgYnkgPSAibGFiZWwiKQ0KY29sbmFtZXMobm9kZXMpWzNdIDwtICJncm91cCINCg0Kbm9kZXMgPC0gYXJyYW5nZShub2Rlcyxncm91cCkNCg0KZWRnZXMkd2lkdGggPC0gcm91bmQoKCgoYWJzKGVkZ2VzJGNvcnJlbGF0aW9uKSAtIFRIUkVTSE9MRCkvKDEgLSBUSFJFU0hPTEQpKzAuMykqTVVMVElQTElFUileKDEuNSkpDQoNCiNlZGdlcyBsZWdlbmQNCmxlZGdlcyA8LSBkYXRhLmZyYW1lKGNvbG9yID0gYygiYmxhY2siLCAiYmxjayIpLA0KIGxhYmVsID0gYygiaGlnaCBjb3JyZWxhdGlvbiIsICJtaWQgY29ycmVsYXRpb24iKSwNCiBkYXNoZXM9YyhGQUxTRSxUUlVFKSwNCiBhcnJvd3M9YygiIiwiIiksDQogd2lkdGg9YygzLDMpLA0KIGZvbnQuYWxpZ24gPSAidG9wIikNCg0KdmlzTmV0d29yayhub2RlcywgZWRnZXMsIGJhY2tncm91bmQgPSAid2hpdGUiKSAlPiUNCiAgdmlzSWdyYXBoTGF5b3V0KGxheW91dCA9ICJsYXlvdXQuZnJ1Y2h0ZXJtYW4ucmVpbmdvbGQiLCByYW5kb21TZWVkID0gMTEpICU+JQ0KICB2aXNOb2RlcyhzaGFkb3cgPSBUUlVFLCBmb250ID0gbGlzdCggc2l6ZT0zMCxzdHJva2VXaWR0aD0xLCBzdHJva2VDb2xvciA9IndoaXRlIikpICU+JQ0KICB2aXNFZGdlcyh0aXRsZSA9ICI8cD5oZWxsbzwvcD4iLCApICU+JQ0KICB2aXNPcHRpb25zKGhpZ2hsaWdodE5lYXJlc3QgPSBsaXN0KGVuYWJsZWQgPSBULCBkZWdyZWUgPSAxLCBob3ZlciA9IFQpLCBub2Rlc0lkU2VsZWN0aW9uID0gVFJVRSkgJT4lDQogIHZpc0xlZ2VuZChhZGRFZGdlcyA9IGxlZGdlcywgdXNlR3JvdXBzID0gRkFMU0UsIG1haW4gPSAiTGVnZW5kIiwgem9vbSA9IEZBTFNFKQ0KYGBgDQoNClBsZWFzZSBub3RlOiBUaGF0IG5vZGVzIGFuZCB0aGUgd2hvbGUgZ3JhcGggY2FuIGJlIG1vdmVkIGJ5IGNsaWNraW5nIGl0LCBhbHNvIGlmIG9uZSB6b29tcyBpbiwNCnNoZS9oZSBjYW4gc2VlIGNvcnJlbGF0aW9uIHZhbHVlcyBiZXR3ZWVuIGVhY2ggbm9kZXMuDQpGcm9tIGFib3ZlIGdyYXBoIHdlIGNhbiBzZWUgd2h5IGFsbCAzOiAibmV1dHJvcGhpbHMiLCAibmV1dHJvcGhpbGlzLmNvdW50IiwgIlguLi5seW1waG9jeXRlIiwgd2VyZSAgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBvdXRjb21lIC0gdGhleSBhcmUgYWxzbyBoaWdobHkgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIuDQoNCldlIHRoaW5rIHRoYXQgaXMgYSBzb21ld2hhdCBiZWF1dGlmdWwgd2F5IHRvIHZpc3VhbGl6ZSBzb21lIG9mIHRoZSBjb25uZWN0aW9uIGJldHdlZW4gZWxlbWVudHMgb2YgdGhlIGJsb29kLiBTb21lIHJlbGF0aW9ucyBhcmUgb2J2aW91cyAoaGVtb2dsb2JpbiBhbmQgaGVtYXRvY3JpdCwgc2VydW0gY2hsb3JpZGUgYW5kIHNlcnVtIHNvZGl1bSksIGJ1dCBzb21lIHdoZXJlIHVuZXhwZWN0ZWQgbGlrZSB0aGUgcmVsYXRpb24gYmV0d2VlbjogYXNwYXJ0YXRlIGFtaW5vdHJhbnNmZXJhc2UgYW5kDQpnbHV0YW1pYy1weXJ1dmljIHRyYW5zYW1pbmFzZSBwcmVzdW1hYmx5IGNvbm5lY3RlZCB0byB0aGUgYmFsYW5jZSBvZiB0aGUgYW1pbmUgaW9ucy4NCg0KDQpgYGB7ciBpbmNsdWRlID0gRkFMU0V9DQpsaWJyYXJ5KHJldGljdWxhdGUpDQpgYGANCg0KIyBBbmFseXNpcyBvZiBwYXJhbWV0ZXIncyBjaGFuZ2UgaW4gdGltZQ0KDQpOb3cgd2Ugd291bGQgbGlrZSB0byBmb2N1cyBob3cgcGF0aWVudCdzIGJsb29kIHBhcmFtZXRlcnMgY2hhbmdlZCB0aHJvdWdob3V0IHRoZWlyIHN0YXkgaW4gdGhlIGhvc3BpdGFsLg0KVG8gZG8gdGhhdCB3ZSBhdmVyYWdlIHBhdGllbnRzJyBibG9vZCByZXN1bHRzIG92ZXIgY29uc2VjdXRpdmUgZGF5cyBzaW5jZSBhZG1pc3Npb24uDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KZGYgPSBwZC5yZWFkX2Nzdigid3VoYW5fYmxvb2Rfc2FtcGxlX2RhdGFfSmFuX0ZlYl8yMDIwLmNzdiIsIHNlcD0iOyIpDQpkZi5QQVRJRU5UX0lELmZpbGxuYShtZXRob2Q9J2ZmaWxsJywgaW5wbGFjZT1UcnVlKQ0KZGZbJ0FkbWlzc2lvbiB0aW1lJ10gPSBwZC50b19kYXRldGltZShkZlsnQWRtaXNzaW9uIHRpbWUnXSkNCmRmWydEaXNjaGFyZ2UgdGltZSddID0gcGQudG9fZGF0ZXRpbWUoZGZbJ0Rpc2NoYXJnZSB0aW1lJ10pDQpkZlsnRHVyYXRpb24gb2Ygc3RheSddID0gZGZbJ0Rpc2NoYXJnZSB0aW1lJ10gLSBkZlsnQWRtaXNzaW9uIHRpbWUnXQ0KZGZbJ0R1cmF0aW9uIG9mIHN0YXknXSA9IGRmWydEdXJhdGlvbiBvZiBzdGF5J10uZHQuZGF5cw0KZGYuaW5zZXJ0KDAsICJkYXlzX2Zyb21fYWRtaXNzaW9uIiwgZGZbIlJFX0RBVEUiXSkNCmRmWyJkYXlzX2Zyb21fYWRtaXNzaW9uIl0gPSBwZC50b19kYXRldGltZShkZlsiZGF5c19mcm9tX2FkbWlzc2lvbiJdKQ0KZGZbImRheXNfZnJvbV9hZG1pc3Npb24iXSA9IChkZlsiZGF5c19mcm9tX2FkbWlzc2lvbiJdIC0gZGZbIkFkbWlzc2lvbiB0aW1lIl0pLmR0LmRheXMNCmRmID0gZGYuZ3JvdXBieShbJ1BBVElFTlRfSUQnLCAnZGF5c19mcm9tX2FkbWlzc2lvbiddLCBhc19pbmRleD1GYWxzZSkubWVhbigpDQpgYGANClNpZGUgbm90ZTogSXQgd291bGQgYmUgYmV0dGVyIHRvIGZpcnN0IGdyb3VwIHBhdGllbnRzIHRoYXQgaGF2ZSBzaW1pbGFyIGxldmVscyBvZiBhbGwgZnV0dXJlcyBvbiBhZG1pc3Npb24gaW4gdGhlIGhvc3BpdGFsIHVzaW5nIHNvbWUga2luZCBvZiBhIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLCBzbyB0aGF0IHdlIHdvdWxkIGNvbXBhcmUgcGF0aWVudCB0aGF0IHdlcmUgaW4gdGhlIHNhbWUgc3RhZ2Ugb2YgQ292aWQtMTkgaW5mZWN0aW9uIG9uIGFkbWlzc2lvbi4gVW5mb3J0dW5hdGVseSB3ZSBoYWQgbm90IGVub3VnaCB0aW1lIHRvIGRvIHRoaXMsIGJ1dCB3ZSBhcmUgYXdhcmUgb2YgdGhlIHByb2JsZW0uDQoNCiMjIFZpc3VhbGl6aW5nIHRoZSBjaGFuZ2UNCg0KUGxvdHRpbmcgZnVuY3Rpb246DQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHNlYWJvcm4gYXMgc25zDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQojIGRlYWRfcGF0aWVudHMgPSBkZltkZi5vdXRjb21lID09IDFdDQojIGFsaXZlX3BhdGllbnRzID0gZGZbZGYub3V0Y29tZSA9PSAwXQ0KDQpkZWYgcGxvdF90cmlwbGVfcGxvdCh5LCB1cF9yZWYsIGxvd2VyX3JlZiwgZGYsIHVuaXQpOg0KICAgIGE0X2RpbXMgPSAoMzUsIDMwKQ0KDQogICAgZmlnLCAoYXgxLCBheDIsIGF4MykgPSBwbHQuc3VicGxvdHMobnJvd3M9MywgZmlnc2l6ZT1hNF9kaW1zKQ0KICAgIHBsdC5yYygnbGVnZW5kJywgZm9udHNpemU9MzApDQogICAgcGx0LnJjUGFyYW1zWydsZWdlbmQudGl0bGVfZm9udHNpemUnXSA9IDMwDQoNCiAgICBwYWxldHRlID0gWyJncmVlbiIsICJyZWQiXQ0KICAgIGJveF9wbG90ID0gc25zLmJveHBsb3QoZGF0YT1kZiwgeD0iZGF5c19mcm9tX2FkbWlzc2lvbiIsIHk9eSwgaHVlPSJvdXRjb21lIiwgc2hvd2ZsaWVycz1GYWxzZSwgYXg9YXgxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZT1wYWxldHRlKQ0KICAgIGJveF9wbG90LmxlZ2VuZF8uc2V0X3RpdGxlKCJIb3NwaXRhbGl0eSBvdXRjb21lOiIpDQogICAgbmV3X2xhYmVscyA9IFsnQWxpdmUnLCAnRGVhZCddDQogICAgZm9yIHQsIGwgaW4gemlwKGJveF9wbG90LmxlZ2VuZF8udGV4dHMsIG5ld19sYWJlbHMpOg0KICAgICAgICB0LnNldF90ZXh0KGwpDQoNCiAgICBzd2FybV9wbG90ID0gc25zLnN3YXJtcGxvdChkYXRhPWRmLCB4PSJkYXlzX2Zyb21fYWRtaXNzaW9uIiwgeT15LCBodWU9Im91dGNvbWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRvZGdlPVRydWUsIHNpemU9Mi4yNSwgYXg9YXgyLCBwYWxldHRlPXBhbGV0dGUpDQogICAgc3dhcm1fcGxvdC5sZWdlbmRfLnNldF90aXRsZSgiSG9zcGl0YWxpdHkgb3V0Y29tZToiKQ0KDQogICAgZm9yIHQsIGwgaW4gemlwKHN3YXJtX3Bsb3QubGVnZW5kXy50ZXh0cywgbmV3X2xhYmVscyk6DQogICAgICAgIHQuc2V0X3RleHQobCkNCiAgICBsaW5lX3Bsb3QgPSBzbnMubGluZXBsb3QoZGF0YT1kZiwgeD0iZGF5c19mcm9tX2FkbWlzc2lvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9eSwgaHVlPSJvdXRjb21lIiwgY2k9OTUsIHBhbGV0dGU9cGFsZXR0ZSwgYXg9YXgzKQ0KDQogICAgbGluZV9wbG90LmNvbGxlY3Rpb25zWzFdLnNldF9sYWJlbCgnOTUlIENvbmZpZGVuY2UgaW50ZXJ2YWwnKQ0KICAgIGxpbmVfcGxvdC5jb2xsZWN0aW9uc1swXS5zZXRfbGFiZWwoIjk1JSBDb25maWRlbmNlIGludGVydmFsIikNCiAgICBsaW5lX3Bsb3QubGVnZW5kKCkNCiAgICBsaW5lX3Bsb3QubGVnZW5kXy5zZXRfdGl0bGUoIkhvc3BpdGFsaXR5IG91dGNvbWU6IikNCg0KICAgIGZvciB0LCBsIGluIHppcChsaW5lX3Bsb3QubGVnZW5kXy50ZXh0cywgbmV3X2xhYmVscyk6DQogICAgICAgIHQuc2V0X3RleHQobCkNCg0KICAgIGxpbmVfcGxvdC5zZXQoeHRpY2tzPXJhbmdlKDAsIDE3KSkNCiAgICBzd2FybV9wbG90LnNldCh4dGlja3M9cmFuZ2UoMCwgMTcpKQ0KICAgIGJveF9wbG90LnNldCh4dGlja3M9cmFuZ2UoMCwgMTcpKQ0KDQogICAgbGluZV9wbG90LnNldCh4bGltPSgtMC41LCAxNikpDQogICAgYm94X3Bsb3Quc2V0KHhsaW09KE5vbmUsIDE2LjUpKQ0KICAgIHN3YXJtX3Bsb3Quc2V0KHhsaW09KE5vbmUsIDE2LjUpKQ0KDQogICAgIyBDaGFuZ2luZyB0aWNrcyBzaXplOg0KICAgIGF4MS50aWNrX3BhcmFtcyhheGlzPSdib3RoJywgbGFiZWxzaXplPTI1KQ0KICAgIGF4Mi50aWNrX3BhcmFtcyhheGlzPSdib3RoJywgbGFiZWxzaXplPTI1KQ0KICAgIGF4My50aWNrX3BhcmFtcyhheGlzPSdib3RoJywgbGFiZWxzaXplPTI1KQ0KDQogICAgIyBQb3NzaWJseSB0aGVyZSBpcyBhIGJldHRlciB3YXkgdG8gZG8gdGhpczoNCiAgICBheDMuc2V0KHhsYWJlbD1Ob25lLCB5bGFiZWw9Tm9uZSkNCiAgICBheDEuc2V0KHhsYWJlbD1Ob25lLCB5bGFiZWw9Tm9uZSkNCiAgICBheDIuc2V0KHhsYWJlbD1Ob25lLCB5bGFiZWw9Tm9uZSkNCiAgICBheDIuc2V0X3lsYWJlbChzdHIoeSkgKyAiICIgKyBmIlt7c3RyKHVuaXQpfV0iLCBmb250c2l6ZT00MCkNCiAgICBheDIuc2V0X3hsYWJlbChmIkJsdWUgZGFzaGVkIGxpbmUgbWFya3MgbG93ZXIgYW5kIHVwcGVyIHJlZmVyZW5jZSB2YWx1ZXMgZm9yIHtzdHIoeSl9IGxldmVscyBpbiBibG9vZC4iLA0KICAgICAgICAgICAgICAgICAgIGZvbnRzaXplPTIwKQ0KICAgIGF4My5zZXRfeGxhYmVsKCJEYXlzIGZyb20gYWRtaXNzaW9uIFtudW1fb2ZfZGF5c10iLCBmb250c2l6ZT00MCkNCg0KICAgICMgUGxvdHRpbmcgcmVmZXJlbmNlIHZhbHVlcw0KICAgIGF4Mi5heGhsaW5lKHVwX3JlZiwgbHM9Jy0tJywgY29sb3I9ImNvcm5mbG93ZXJibHVlIikNCiAgICBheDIuYXhobGluZShsb3dlcl9yZWYsIGxzPSctLScsIGNvbG9yPSJjb3JuZmxvd2VyYmx1ZSIpDQoNCiAgICAjIFNob3dpbmcgdGhlIHBsb3QNCiAgICBwbHQuc2hvdygpDQoNCmRmID0gZGYuZ3JvdXBieShbJ1BBVElFTlRfSUQnLCAnZGF5c19mcm9tX2FkbWlzc2lvbiddLCBhc19pbmRleD1GYWxzZSkubWVhbigpDQpkZi5kYXlzX2Zyb21fYWRtaXNzaW9uID0gZGYuZGF5c19mcm9tX2FkbWlzc2lvbi5hcHBseShsYW1iZGEgeDogaW50KHgpKQ0KYGBgDQpQbG90dGluZyBmdW5jdGlvbiB2aXN1YWxpemVzIGdpdmVuIGJsb29kIHBhcmFtZXRlciBjaGFuZ2Ugb3ZlciB0aW1lLg0KVGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVyIGFyZSBhdmVyYWdlZCBldmVyeSBjb25zZWN1dGl2ZSBkYXkgZnJvbSBhZG1pc3Npb24uDQpUaGUgZmlndXJlIGNvbnNpc3RzIG9mIHRocmVlIGNoYXJ0czogYm94cGxvdCBjaGFydCwgc3dhcm0tcGxvdCBjaGFydCwgYW5kIGxpbmUtcGxvdC4NClRoZSBkZWNpc2lvbiB3YXMgbWFkZSB0byBzaG93IGFsbCB0aHJlZSBmb3JtcywgYmVjYXVzZSBzaW5nbGUgcGxvdCBjYW4gYmUgdmVyeSBtaXNsZWFkaW5nLCBkdWUgdG8gbGFjayBvZiBkYXRhLg0KKEVzcGVjaWFsbHkgYm94IHBsb3RzIHRpY2tzIGdlbmVyYXRlZCBmb3IgZXhhbXBsZSBmb3Igb25seSBjb3VwbGUgb2YgZGF0YSBwb2ludHMuKQ0KU3VjaCBmaWd1cmUgY2FuIGJlIGdlbmVyYXRlZCBmb3IgYW55IGJsb29kIHBhcmFtZXRlciBwcmVzZW50IGluIHRoZSBkYXRhLCBidXQgd2Ugc2hvdyBvbmx5IHNvbWUgb2YgdGhlIG1vc3QgaW50ZXJlc3Rpbmcgb25lczoNCmBgYHtweXRob259DQpwbG90X3RyaXBsZV9wbG90KCJoZW1vZ2xvYmluIiwgMTMwLCAxNzUsIGRmLCB1bml0PSJnL0wiKQ0KYGBgDQpPbmUgd291bGQgZXhwZWN0IHRoYXQgcGF0aWVudHMgaW5mZWN0ZWQgd2l0aCBDb3ZpZC0xOSB3b3VsZCBoYXZlIGhpZ2hlciBoZW1vZ2xvYmluIGNvbmNlbnRyYXRpb24gdG8NCmNvbXBlbnNhdGUgZm9yIGRhbWFnZWQgbHVuZydzIHRpc3N1ZS4gV2UgY291bGRuJ3QgbWFrZSBzdWNoIG9ic2VydmF0aW9uLg0KYGBge3B5dGhvbn0NCnBsb3RfdHJpcGxlX3Bsb3QoIiglKWx5bXBob2N5dGUiLCAxMCwgNDUsIGRmLCB1bml0PSIlIikNCmBgYA0KVGhpcyBzZWVtcyB0byBiZSBwcmV0dHkgc3RyYWlnaHQgZm9yd2FyZCwgdGhlIG1vcmUgbHltcGhvY3l0ZSByZXNwb25zaWJsZSBmb3IgaHVtb3JhbCBpbW11bmUgcmVzcG9uc2UgdGhlIHBhdGllbnQgaGF2ZSB0aGUgbW9yZSBsaWtlbHkgaGUgaXMgdG8gc3Vydml2ZS4NCmBgYHtweXRob259DQpwbG90X3RyaXBsZV9wbG90KCJjYWxjaXVtIiwgMi4xMiwgMi42MiwgZGYsIHVuaXQ9Im1tb2wvbCAiKQ0KYGBgDQpUaGlzIG9ic2VydmF0aW9uIHdhcyBhbHNvIGV4cGVjdGVkLCBhcyBjYWxjaXVtIGlvbnMgcGxheSBhIHZlcnkgaW1wb3J0YW50IHJvbGUgaW4gdGhlIGltbXVuZSByZXNwb25zZS4NCmBgYHtweXRob259DQpwbG90X3RyaXBsZV9wbG90KCJhbGJ1bWluIiwgMzUsIDU1LCBkZiwgdW5pdD0iZy9MIikNCmBgYA0KVGhpcyBpcyB2ZXJ5IHNob2NraW5nLCBhcyB0aGUgcHJvdGVpbiBhbGJ1bWluIGlzIG5vdCBkaXJlY3RseSBjb25uZWN0ZWQgdG8gaW1tdW5lLXJlc3BvbnNlLlwNCldlIGZvdW5kIHNpbWlsYXIgb2JzZXJ2YXRpb24gc3VnZ2VzdGluZyB0aGF0IGFsYnVtaW4gbGV2ZWxzIGFyZSBhIHZlcnkgZ29vZCBpbmRpY2F0b3Igb2YgdGhlIHBhdGllbnQncyBoZWFsdGggY29uZGl0aW9uLg0KRm91bmQgYXJ0aWNsZSBwcm9wb3NlZCBldmVuIGFsYnVtaW4gaW5qZWN0aW9ucy4NCltMaW5rIHRvIHRoZSBmb3VuZCBhcnRpY2xlXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM3MjczMDYwLykNCg0KYGBge3B5dGhvbiBpbmNsdWRlPUZBTFNFfQ0KI1Jlc2V0aW5nIHRoZSBkYXRhZnJhbWUNCmRmID0gcGQucmVhZF9jc3YoInd1aGFuX2Jsb29kX3NhbXBsZV9kYXRhX0phbl9GZWJfMjAyMC5jc3YiLCBzZXA9IjsiKQ0KZGYuUEFUSUVOVF9JRC5maWxsbmEobWV0aG9kPSdmZmlsbCcsIGlucGxhY2U9VHJ1ZSkNCmRmWydBZG1pc3Npb24gdGltZSddID0gcGQudG9fZGF0ZXRpbWUoZGZbJ0FkbWlzc2lvbiB0aW1lJ10pDQpkZlsnRGlzY2hhcmdlIHRpbWUnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWydEaXNjaGFyZ2UgdGltZSddKQ0KZGYuaW5zZXJ0KDAsICJkYXlzX2Zyb21fYWRtaXNzaW9uIiwgZGZbIlJFX0RBVEUiXSkNCmRmWyJkYXlzX2Zyb21fYWRtaXNzaW9uIl0gPSBwZC50b19kYXRldGltZShkZlsiZGF5c19mcm9tX2FkbWlzc2lvbiJdKQ0KZGZbImRheXNfZnJvbV9hZG1pc3Npb24iXSA9IChkZlsiZGF5c19mcm9tX2FkbWlzc2lvbiJdIC0gZGZbIkFkbWlzc2lvbiB0aW1lIl0pLmR0LmRheXMNCmRmID0gZGYuZ3JvdXBieSgiUEFUSUVOVF9JRCIpLm1lYW4oKQ0KZGYgPSBkZi5kcm9wKGNvbHVtbnM9WyJkYXlzX2Zyb21fYWRtaXNzaW9uIl0pDQpgYGANCg0KIyBUcnlpbmcgdG8gdmlzdWFsaXplIG1vcmUgdGhhbiB0d28gZmVhdHVyZXMNClNjYXR0ZXIgcGxvdCBzZWVtIHRvIGJlIHNpbXBsZSwgYnV0IGl0IHdhcyBhYmxlIHRvIHByb3ZpZGUgdmFsdWFibGUgaW5zaWdodHM6DQoNCiMjIE5vdCB0aGF0IHNpbXBsZSBzY2F0dGVyIHBsb3RzDQoNCmBgYHtweXRob24gaW5jbHVkZT1GQUxTRX0NCmRlZiBhX2Z1bmN0aW9uKGFsYnVtaW5fdmFsdWUpOg0KICAgIGlmIGFsYnVtaW5fdmFsdWUgPCAzNS4wOg0KICAgICAgICByZXR1cm4gImJlbG93Ig0KICAgIGVsaWYgYWxidW1pbl92YWx1ZSA+IDU1LjA6DQogICAgICAgIHJldHVybiAiYWJvdmUiDQogICAgZWxzZToNCiAgICAgICAgcmV0dXJuICJpbl9ub3JtIg0KZGYub3V0Y29tZSA9IGRmLm91dGNvbWUuYXBwbHkobGFtYmRhIHg6ICJkZWFkIiBpZiB4ID09IDEgZWxzZSAiYWxpdmUiKQ0KZGYuYWxidW1pbiA9IGRmLmFsYnVtaW4uYXBwbHkoYV9mdW5jdGlvbikNCmBgYA0KUGxvdHRpbmcgZnVuY3Rpb246DQpgYGB7cHl0aG9ufQ0KZGVmIHBsb3QoeCwgeSwgc2l6ZSwgc2l6ZXMsIG5ld19sYWJlbHMsIHhfbGltLCB0aXRsZSwgZGYpOg0KICAgIGE0X2RpbXMgPSAoMTcsIDguMjcpDQogICAgZmlnLCBheCA9IHBsdC5zdWJwbG90cyhmaWdzaXplPWE0X2RpbXMpDQogICAgcGFsZXR0ZSA9IFsiZ3JlZW4iLCAicmVkIl0NCiAgICBzID0gc25zLnNjYXR0ZXJwbG90KGRhdGE9ZGYsIHg9eCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHk9eSwgaHVlPSJvdXRjb21lIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9c2l6ZSwgc2l6ZXM9c2l6ZXMsDQogICAgICAgICAgICAgICAgICAgICAgICBwYWxldHRlPXBhbGV0dGUsIGF4PWF4KQ0KICAgIHMuc2V0KHhsaW09KHhfbGltLCBOb25lKSkNCiAgICBwbHQubGVnZW5kKGxvYz0xLCBwcm9wPXsnc2l6ZSc6IDE1fSkNCiAgICBmb3IgdGV4dCwgbmV3X2xhYmVsIGluIHppcChzLmxlZ2VuZF8udGV4dHMsIG5ld19sYWJlbHMpOg0KICAgICAgICB0ZXh0LnNldF90ZXh0KG5ld19sYWJlbCkNCiAgICBwbHQudGl0bGUodGl0bGUsIGZvbnRzaXplPTIyKQ0KICAgIHBsdC54bGFiZWwoeGxhYmVsPXgsIGZvbnRzaXplPTE4KQ0KICAgIHBsdC55bGFiZWwoeWxhYmVsPXksIGZvbnRzaXplPTE4KQ0KICAgIHBsdC5zaG93KCkNCmBgYA0KVGhlIGZpZ3VyZSBzaG93cyB0aGUgcmVsYXRpb24gYmV0d2VlbiAzIGZlYXR1cmVzIGFuZCB0aGUgcGF0aWVudCBvdXRjb21lLiAoeC1heGlzLCB5LWF4aXMsIHNpemUgb2YgbWFya2VycywgYW5kIGNvbG9yIG9mIG1hcmtlcnMpDQpgYGB7cHl0aG9ufQ0KeCwgeSwgc2l6ZSA9ICJuZXV0cm9waGlscyBjb3VudCIsICIoJSlseW1waG9jeXRlIiwgImFsYnVtaW4iDQpzaXplcyA9ICgzMCwgMTAwKQ0KbmV3X2xhYmVscyA9IFsnSG9zcGl0YWxpdHkgb3V0Y29tZTonLCAnQWxpdmUnLCAnRGVhZCcsDQogICAgICAgICAgICAgICdBbGJ1bWluIGxldmVsczonLCAnYmVsb3cgbm9ybScsICdpbiBub3JtJ10NCnhfbGltID0gMA0KdGl0bGUgPSBOb25lDQpwbG90KHgsIHksIHNpemUsIHNpemVzLCBuZXdfbGFiZWxzLCB4X2xpbSwgdGl0bGUsIGRmKQ0KYGBgDQpQYXRpZW50cyB3aXRoIGxvdyBseW1waG9jeXRlIGxldmVscywgaGlnaCBuZXV0cm9waGlscyBjb3VudCBhbmQgYWxidW1pbiBsZXZlbHMgYmVsb3cgdGhlIG5vcm0gYXJlIGFsbW9zdCBjZXJ0YWluIHRvIGRpZS4NCmBgYHtweXRob259DQp4LCB5LCBzaXplID0gIkxhY3RhdGUgZGVoeWRyb2dlbmFzZSIsICJlR0ZSIiwgIkhpZ2ggc2Vuc2l0aXZpdHkgQy1yZWFjdGl2ZSBwcm90ZWluIg0Kc2l6ZXMgPSAoMjAsIDE1MCkNCm5ld19sYWJlbHMgPSBbJ0hvc3BpdGFsaXR5IG91dGNvbWU6JywgJ0FsaXZlJywgJ0RlYWQnLA0KICAgICAgICAgICAgICAnSGlnaCBzZW5zaXRpdml0eSBcbkMtcmVhY3RpdmUgcHJvdGVpbiBsZXZlbHM6J10NCnhfbGltID0gNzUNCnRpdGxlID0gTm9uZQ0KcGxvdCh4LCB5LCBzaXplLCBzaXplcywgbmV3X2xhYmVscywgeF9saW0sIHRpdGxlLCBkZikNCmBgYA0KVGhpcyBpcyBzb21ld2hhdCB1bmV4cGVjdGVkLCBhcyBDLXJlYWN0aXZlIHByb3RlaW4gIGJlaW5nIHRoZSBwYXJ0IG9mIHRoZSBjb21wbGVtZW50IHN5c3RlbSBvZiB0aGUgaW1tdW5lIHN5c3RlbSBzZWVtcw0KdG8gcGxheSBhIHJvbGUgaW4gdGhlIGltbXVuZSByZXNwb25zZSwgYnV0IHRoZSByaXNlIG9mIGl0J3MgbGV2ZWwgc2VlbXMgdG8gbGVhZCB0byBkZWF0aC4gTGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIGlzIGEgYWxzbyBhDQpnb29kIG91dGNvbWUgaW5kaWNhdG9yLg0KYGBge3B5dGhvbiBpbmNsdWRlPUZBTFNFfQ0KZGYgPSBwZC5yZWFkX2Nzdigid3VoYW5fYmxvb2Rfc2FtcGxlX2RhdGFfSmFuX0ZlYl8yMDIwLmNzdiIsIHNlcD0iOyIpDQoNCmRmLlBBVElFTlRfSUQuZmlsbG5hKG1ldGhvZD0nZmZpbGwnLCBpbnBsYWNlPVRydWUpDQpkZlsnQWRtaXNzaW9uIHRpbWUnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWydBZG1pc3Npb24gdGltZSddKQ0KZGZbJ0Rpc2NoYXJnZSB0aW1lJ10gPSBwZC50b19kYXRldGltZShkZlsnRGlzY2hhcmdlIHRpbWUnXSkNCmRmLmluc2VydCgwLCAiZGF5c19mcm9tX2FkbWlzc2lvbiIsIGRmWyJSRV9EQVRFIl0pDQpkZlsiZGF5c19mcm9tX2FkbWlzc2lvbiJdID0gcGQudG9fZGF0ZXRpbWUoZGZbImRheXNfZnJvbV9hZG1pc3Npb24iXSkNCmRmWyJkYXlzX2Zyb21fYWRtaXNzaW9uIl0gPSAoZGZbImRheXNfZnJvbV9hZG1pc3Npb24iXSAtIGRmWyJBZG1pc3Npb24gdGltZSJdKS5kdC5kYXlzDQojZGYgPSBkZi5ncm91cGJ5KFsnUEFUSUVOVF9JRCcsICdkYXlzX2Zyb21fYWRtaXNzaW9uJ10sIGFzX2luZGV4PUZhbHNlKS5tZWFuKCkNCmBgYA0KDQojIENyZWF0aW5nIGEgc2ltcGxlIG1vZGVsIHByZWRpY3RpbmcgdGhlIG91dGNvbWUNCg0KTm93IHdlIGFyZSB0cnlpbmcgdG8gY3JlYXRlIGEgbW9kZWwgdGhhdCBwcmVkaWN0cyB0aGUgcGF0aWVudCBvdXRjb21lLA0Kd2UgaGF2ZSBwaWNrZWQgMyBmZWF0dXJlcyAobWVudGlvbmVkIGFib3ZlKSB0aGF0IHNlZW1lZCB0byBiZSB0aGUgbW9zdCBpbXBvcnRhbnQuDQoNCiMjIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgbW9kZWwNCg0KV2UgcGlja2VkIHRoaXMgbW9kZWwgZm9yIHRoZSBmb2xsb3dpbmcgcmVhc29uOiBcDQpSZXN1bHRzIG9mIHRoZSBtb2RlbCBjYW4gYmUgdmlzdWFsaXplZCB1c2luZyAgYSBoeXBlci1wbGFuZS4gXA0KDQpXZSBhcmUgYXdhcmUgdGhhdCBwcmVkaWN0aW5nIHBhdGllbnQgb3V0Y29tZSBiYXNlZCBvbiB0aGVzZSBwYXJhbWV0ZXJzIGlzIHNvbWV3aGF0IHVucHJvZHVjdGl2ZQ0KYXMgdGhlc2UgcGFyYW1ldGVycyBhcmUgd2VsbCBrbm93biBkZXRlcmlvcmF0aW5nIGhlYWx0aCBjb25kaXRpb24gcHJlZGljdG9ycyBpbiB0aGUgbWVkaWNhbCBjb21tdW5pdHkuIFRoaXMgaXMgbW9yZSB0byBsZWFybiBkYXRhIHZpc3VhbGl6YXRpb24gdGhhbiB0byBjcmVhdGUgYSB1c2VmdWwgbWFjaGluZS1sZWFybmluZw0KYXBwbGljYXRpb24uDQoNCkRhdGEgcHJlcHJvY2Vzc2luZzoNCmBgYHtweXRob259DQppbXBvcnQgbnVtcHkgYXMgbnANCnh4ID0gZGZbJ0xhY3RhdGUgZGVoeWRyb2dlbmFzZSddDQp5eSA9IGRmWydlR0ZSJ10NCnp6ID0gZGZbJ0hpZ2ggc2Vuc2l0aXZpdHkgQy1yZWFjdGl2ZSBwcm90ZWluJ10NCmh1ZSA9IGRmLm91dGNvbWUNCmh1ZSA9IFsicmVkIiBpZiBvdXRjb21lID09IDEgZWxzZSAiZ3JlZW4iIGZvciBvdXRjb21lIGluIG5wLmFycmF5KGh1ZSldDQpkZiA9IGRmLmdyb3VwYnkoIlBBVElFTlRfSUQiKS5tZWFuKCkNCmRmID0gZGYuZHJvcChjb2x1bW5zPVsiZGF5c19mcm9tX2FkbWlzc2lvbiJdKQ0KZGYgPSBkZltkZlsnTGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlJ10ubm90bmEoKV0NCmRmID0gZGZbZGZbJ2VHRlInXS5ub3RuYSgpXQ0KZGYgPSBkZltkZlsiSGlnaCBzZW5zaXRpdml0eSBDLXJlYWN0aXZlIHByb3RlaW4iXS5ub3RuYSgpXQ0KWCA9IGRmW1siTGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlIiwgImVHRlIiLCAiSGlnaCBzZW5zaXRpdml0eSBDLXJlYWN0aXZlIHByb3RlaW4iXV0NCnkgPSBkZi5vdXRjb21lDQpgYGANClRyYWluaW5nIHRoZSBtb2RlbDoNCmBgYHtweXRob259DQpmcm9tIHNrbGVhcm4ubW9kZWxfc2VsZWN0aW9uIGltcG9ydCB0cmFpbl90ZXN0X3NwbGl0DQpmcm9tIHNrbGVhcm4gaW1wb3J0IHN2bQ0KIyBTcGxpdHRpbmcgZGF0YQ0KWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QgPSB0cmFpbl90ZXN0X3NwbGl0KA0KICAgIFgsIHksIHRlc3Rfc2l6ZT0wLjMzKQ0KDQojIENyZWF0aW5nIGFuZCBmaXR0aW5nIHRoZSBtb2RlbA0KbW9kZWwgPSBzdm0uU1ZDKGtlcm5lbD0nbGluZWFyJywgcmFuZG9tX3N0YXRlPTIpDQpjbGYgPSBtb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikNCnNjb3JlID0gbW9kZWwuc2NvcmUoWF90ZXN0LCB5X3Rlc3QpDQpwcmludCgiJTAuM2YiICUgc2NvcmUpDQoNCmBgYA0KVGhlIG1vZGVsIHNjb3JlIGlzIHZlcnkgaGlnaCAoIFRoZXJlIGlzIGEgcHJldHR5IGdvb2QgY2hhbmNlIHRoYXQgd2Ugb3Zlci1maXQpDQoNCiMjIDMtRCB2aXN1YWxpemF0aW9uIG9mIHRoZSBtb2RlbCByZXN1bHRzDQoNCk5vdyB3ZSB0cnkgdG8gdmlzdWFsaXplIHRoZSBwbGFuZSB0aGF0IGRpdmlkZXMgcG9pbnRzIGRlc2NyaWJlZCBieSB0aGUgdGhyZWUgZ2l2ZW4gZmVhdHVyZXMuDQpUaGlzIHdhcyBub3QgZnVuLCBhbmQgbm93IHdlIHVuZGVyc3RhbmQgaW4gZnVsbCB3aHkgaXMgaXQgYmV0dGVyIHRvIHN0YXkgYXdheSBmcm9tIDNELg0KVW5mb3J0dW5hdGVseSwgci1tYXJrZG93biBzZWVtcyB0byBkaXNhYmxlIGludGVyYWN0aXZpdHkgb2YgdGhlIHBsb3QuIChUaGUgZmlndXJlIGNhbiBiZSBtb3ZlZCBpbiBqdXB5dGVyIGVudmlyb25tZW50KS4NCmBgYHtweXRob259DQojIFRoaXMgaXMgbm90IGFzIGludHVpdGl2ZSBhcyBJIHRob3VnaHQuDQp0ZW1wID0gbnAubGluc3BhY2UoMCwgNzUwLCA1MCkNCngsIHkgPSBucC5tZXNoZ3JpZCh0ZW1wLCB0ZW1wKQ0KDQojIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vdXNlcnMvMTQzMDgyOS9tYXR0LWhhbmNvY2sgVGhhbmtzIHRvIGxpbWl0bGVzcyBrbm93bGVkZ2Ugb2YgTWF0dCBIYW5jb2NrLg0KeiA9IGxhbWJkYSB4LCB5OiAoLWNsZi5pbnRlcmNlcHRfWzBdIC0gY2xmLmNvZWZfWzBdWzBdICogeCAtIGNsZi5jb2VmX1swXVsxXSAqIHkpIC8gY2xmLmNvZWZfWzBdWzJdDQoNCmZpZyA9IHBsdC5maWd1cmUoKQ0KYXggPSBmaWcuYWRkX3N1YnBsb3QoMTExLCBwcm9qZWN0aW9uPSczZCcpDQp0ID0gYXguc2NhdHRlcih4eCwgeXksIHp6LCBjPWh1ZSkNCmF4LnBsb3Rfc3VyZmFjZSh4LCB5LCB6KHgsIHkpKQ0KYXguc2V0X3hsYWJlbCgnTGFjdGF0ZSBkZWh5ZHJvZ2VuYXNlJykNCmF4LnNldF95bGFiZWwoJ2VHRlInKQ0KYXguc2V0X3psYWJlbCgnSGlnaCBzZW5zaXRpdml0eSBDLXJlYWN0aXZlIHByb3RlaW4nKQ0KZHVtbXkgPSBheC5zZXRfeGxpbTNkKDAsIE5vbmUpDQpkdW1teSA9IGF4LnNldF95bGltM2QoMCwgTm9uZSkNCmR1bW15ID0gYXguc2V0X3psaW0zZCgwLCBOb25lKQ0KYXgudmlld19pbml0KDIxLCAtMTMzKSAgIyAyNiwgNDUgIzI1IC0xMjQNCmF4LnNldF90aXRsZSgiR3JlZW4gcG9pbnRzIC0gYWxpdmUgcGF0aWVudHMsIHJlZCBwb2ludHMgLSBkZWFkIHBhdGllbnRzLFxuICINCiAgICAgICAgICAgICAiYmx1ZSBwbGFuZSBiYXNlZCBvbiBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIG1vZGVsLiIpDQpwbHQuc2hvdygpDQpgYGANClRoZSBwbGFuZSBkaXZpZGVzIHJlZCBhbmQgZ3JlZW4gcG9pbnRzIHZlcnkgZWZmaWNpZW50bHksIHZpc3VhbGl6aW5nIGdyYXBoaWNhbGx5IHRoZSBtb2RlbC4NCg0KVGhhbmsgeW91IGZvciByZWFkaW5nLg0KDQojIFNvdXJjZXM6DQoqICJodHRwczovL3d3dy5zdGF0d29yeC5jb20vY2gvYmxvZy9pbnRlcmFjdGl2ZS1uZXR3b3JrLXZpc3VhbGl6YXRpb24td2l0aC1yLyINCiogImh0dHBzOi8vZGF0YXN0b3JtLW9wZW4uZ2l0aHViLmlvL3Zpc05ldHdvcmsvaWdyYXBoLmh0bWwiDQoqICJodHRwOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL3dpa2kvZ2dwbG90Mi1xdWljay1jb3JyZWxhdGlvbi1tYXRyaXgtaGVhdG1hcC1yLXNvZnR3YXJlLWFuZC1kYXRhLXZpc3VhbGl6YXRpb24iDQoqIGFuZCBtb3JlIGluIGNvZGUgY29tbWVudHM=